package top.z.work.framework.web.filter;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.util.ContentCachingRequestWrapper;
import top.z.work.framework.common.exception.GlobalErrorCode;
import top.z.work.framework.common.pojo.R;
import top.z.work.framework.common.util.JsonUtil;
import top.z.work.framework.common.util.ServletUtil;
import top.z.work.framework.web.annotation.AccessLogger;
import top.z.work.framework.web.util.WebFrameworkUtil;
import top.z.work.module.system.api.logger.SystemLoggerAccessApi;
import top.z.work.module.system.api.logger.dto.SystemLoggerAccessDto;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;

import static top.z.work.framework.web.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD;

/**
 * API 访问日志 Filter
 * 记录 API 访问日志到数据库中
 */
@Slf4j
public class LoggerAccessFilter extends OncePerRequestFilter {

    private final SystemLoggerAccessApi systemLoggerAccessApi;

    public LoggerAccessFilter(SystemLoggerAccessApi systemLoggerAccessApi) {
        this.systemLoggerAccessApi = systemLoggerAccessApi;
    }

    /**
     * 只过滤指定URL开头
     */
    /*@Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAdminApi().getPrefix(), webProperties.getAppApi().getPrefix());
    }*/
    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws ServletException, IOException {
        var requestWrapper = new ContentCachingRequestWrapper(request);

        // 获得开始时间
        LocalDateTime beginTime = LocalDateTime.now();
        // 获取参数
        var parameter = ServletUtil.getParameter(requestWrapper);
        var body      = ServletUtil.isJsonRequest(requestWrapper) ? new String(requestWrapper.getContentAsByteArray(), requestWrapper.getCharacterEncoding()) : null;

        try {
            // 继续过滤器
            filterChain.doFilter(request, response);
            // 正常执行, 记录日志
            createAccessLogger(requestWrapper, beginTime, parameter, body, null);
        } catch (Exception ex) {
            log.error("异常执行, 记录日志");
            createAccessLogger(requestWrapper, beginTime, parameter, body, ex);
            throw ex;
        }
    }

    private void createAccessLogger(
            HttpServletRequest request,
            LocalDateTime beginTime,
            Map<String, String> parameter,
            String requestBody,
            Exception ex
    ) {
        SystemLoggerAccessDto loggerAccessDto = new SystemLoggerAccessDto();
        try {
            boolean enable = buildApiAccessLog(loggerAccessDto, request, beginTime, parameter, requestBody, ex);
            if (!enable) {
                return;
            }
            systemLoggerAccessApi.createLoggerAccess(loggerAccessDto);
        } catch (Throwable th) {
            log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), JsonUtil.toJsonString(loggerAccessDto), th);
        }
    }

    private boolean buildApiAccessLog(
            SystemLoggerAccessDto accessLog,
            HttpServletRequest request,
            LocalDateTime beginTime,
            Map<String, String> parameter,
            String requestBody, Exception ex
    ) {
        // 判断：是否要记录操作日志
        HandlerMethod handlerMethod          = (HandlerMethod) request.getAttribute(ATTRIBUTE_HANDLER_METHOD);
        AccessLogger  accessLoggerAnnotation = null;
        if (handlerMethod == null) {
            return false;
        } else {
            accessLoggerAnnotation = handlerMethod.getMethodAnnotation(AccessLogger.class);
            if (accessLoggerAnnotation == null) {
                return false;
            }
            // OPTIONS 请求不记录
            if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
                return false;
            }
            if (BooleanUtil.isFalse(accessLoggerAnnotation.enable())) {
                return false;
            }
        }

        // 处理用户信息
        if (StpUtil.isLogin()) {
            accessLog.setUserId(Long.valueOf(StpUtil.getLoginId().toString()));
        }
        // 设置访问结果
        R<?> result = WebFrameworkUtil.getResult(request);
        if (result != null) {
            accessLog.setResultCode(result.getCode()).setResultMessage(result.getMessage());
        } else if (ex != null) {
            accessLog.setResultCode(GlobalErrorCode.INTERNAL_SERVER_ERROR.code()).setResultMessage(ExceptionUtil.getRootCauseMessage(ex));
        } else {
            accessLog.setResultCode(GlobalErrorCode.SUCCESS.code()).setResultMessage(GlobalErrorCode.SUCCESS.message());
        }
        // 设置请求字段
        accessLog
                .setRequestUrl(request.getRequestURI())
                .setRequestMethod(request.getMethod())
                .setUserAgent(ServletUtil.getUserAgent(request))
                .setRequestIp(ServletUtil.getClientIP());
        Boolean requestEnable = accessLoggerAnnotation.requestEnable();
        if (BooleanUtil.isTrue(requestEnable)) {
            // 默认记录, 所以判断 !false
            Map<String, Object> requestParameter = MapUtil.<String, Object>builder()
                    .put("query", parameter)
                    .put("body", requestBody)
                    .build();
            accessLog.setRequestParameter(JsonUtil.toJsonString(requestParameter));
        }
        Boolean responseEnable = accessLoggerAnnotation.responseEnable();
        if (BooleanUtil.isTrue(responseEnable)) {
            // 默认不记录, 默认强制要求 true
            accessLog.setResponseBody(JsonUtil.toJsonString(result));
        }
        // 持续时间
        accessLog
                .setBeginTime(beginTime)
                .setEndTime(LocalDateTime.now())
                .setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS));

        // 操作模块
        accessLog
                .setOperateName(accessLoggerAnnotation.title())
                .setOperateModule(accessLoggerAnnotation.module())
                .setOperateType(accessLoggerAnnotation.type().getType());
        return true;
    }

}
